https://www.tidytextmining.com/

load libraries

library(rtweet)
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages ------------------------------------------------------------------- tidyverse 1.3.0 --
v ggplot2 3.3.2     v purrr   0.3.4
v tibble  3.0.4     v dplyr   1.0.2
v tidyr   1.1.2     v stringr 1.4.0
v readr   1.4.0     v forcats 0.5.0
-- Conflicts ---------------------------------------------------------------------- tidyverse_conflicts() --
x dplyr::filter()  masks stats::filter()
x purrr::flatten() masks rtweet::flatten()
x dplyr::lag()     masks stats::lag()
library(tidytext)
library(wordcloud2)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Get Tweets

search_tweets

tweet_collection <- search_tweets("marchmadness", n=1000, lang = "en")
Requesting token on behalf of user...
Waiting for authentication in browser...
Press Esc/Ctrl + C to abort
Authentication complete.

Downloading [=======>---------------------------------]  20%
Downloading [===========>-----------------------------]  30%
Downloading [===============>-------------------------]  40%
Downloading [===================>---------------------]  50%
Downloading [========================>----------------]  60%
Downloading [============================>------------]  70%
Downloading [================================>--------]  80%
Downloading [====================================>----]  90%
Downloading [=========================================] 100%
tweet_collection <- tweet_collection %>% 
  filter(is_retweet == "FALSE")
tweet_collection

Tokenize tweets

tweets_by_tweeter <- tweet_collection %>% 
  group_by(screen_name) %>% 
  mutate(line = row_number()) %>% 
  ungroup()

tweets_by_tweeter %>% 
  count(screen_name, sort = TRUE)

glimpse(tweets_by_tweeter)
Rows: 321
Columns: 91
$ user_id                 <chr> "1321835395231920129", "85961526", "85961526", "85961526", "85961526", ...
$ status_id               <chr> "1321838635323445248", "1321838549977751552", "1321113228781518848", "1...
$ created_at              <dttm> 2020-10-29 15:37:43, 2020-10-29 15:37:23, 2020-10-27 15:35:13, 2020-10...
$ screen_name             <chr> "koenig_reese", "TheAndyKatz", "TheAndyKatz", "TheAndyKatz", "TheAndyKa...
$ text                    <chr> "@marchmadness @TheAndyKatz @ZagMBB @NovaMBB @DukeMBB @TexasMBB @MSU_Ba...
$ source                  <chr> "Twitter for iPhone", "Twitter Web App", "Twitter for iPhone", "Twitter...
$ display_text_width      <dbl> 18, 92, 181, 120, 101, 27, 247, 65, 6, 220, 15, 25, 28, 15, 19, 63, 4, ...
$ reply_to_status_id      <chr> "1321834235561451520", NA, NA, NA, NA, "1321834235561451520", NA, "1321...
$ reply_to_user_id        <chr> "202416362", NA, NA, NA, NA, "202416362", NA, "20179206", "202416362", ...
$ reply_to_screen_name    <chr> "marchmadness", NA, NA, NA, NA, "marchmadness", NA, "peterp2000", "marc...
$ is_quote                <lgl> FALSE, TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE...
$ is_retweet              <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, F...
$ favorite_count          <int> 0, 1, 11, 7, 0, 0, 2, 0, 0, 1, 0, 0, 7, 1, 0, 0, 0, 8, 21, 33, 1223, 12...
$ retweet_count           <int> 0, 0, 6, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7, 4, 223, 14, 0,...
$ quote_count             <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ reply_count             <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ hashtags                <list> [NA, NA, NA, NA, NA, "WVU", NA, NA, NA, NA, NA, NA, <"ZagUp", "GoZags"...
$ symbols                 <list> [NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
$ urls_url                <list> [NA, "twitter.com/marchmadness/s…", "twitter.com/marchmadness/s…", "fa...
$ urls_t.co               <list> [NA, "https://t.co/N2Oh7TsEyY", "https://t.co/9cdID9pfPQ", "https://t....
$ urls_expanded_url       <list> [NA, "https://twitter.com/marchmadness/status/1321834235561451520?s=20...
$ media_url               <list> [NA, NA, NA, NA, NA, NA, NA, NA, "http://pbs.twimg.com/tweet_video_thu...
$ media_t.co              <list> [NA, NA, NA, NA, NA, NA, NA, NA, "https://t.co/vmpqMz28ZE", NA, NA, NA...
$ media_expanded_url      <list> [NA, NA, NA, NA, NA, NA, NA, NA, "https://twitter.com/Tonycastilla99/s...
$ media_type              <list> [NA, NA, NA, NA, NA, NA, NA, NA, "photo", NA, NA, NA, NA, NA, NA, NA, ...
$ ext_media_url           <list> [NA, NA, NA, NA, NA, NA, NA, NA, "http://pbs.twimg.com/tweet_video_thu...
$ ext_media_t.co          <list> [NA, NA, NA, NA, NA, NA, NA, NA, "https://t.co/vmpqMz28ZE", NA, NA, NA...
$ ext_media_expanded_url  <list> [NA, NA, NA, NA, NA, NA, NA, NA, "https://twitter.com/Tonycastilla99/s...
$ ext_media_type          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ mentions_user_id        <list> [<"202416362", "85961526", "602989093", "1581277519", "18272699", "188...
$ mentions_screen_name    <list> [<"marchmadness", "TheAndyKatz", "ZagMBB", "NovaMBB", "DukeMBB", "Texa...
$ lang                    <chr> "en", "en", "en", "en", "en", "en", "en", "en", "en", "en", "en", "en",...
$ quoted_status_id        <chr> NA, "1321834235561451520", "1321100218943840261", NA, NA, NA, "13218342...
$ quoted_text             <chr> NA, "Who will have a breakout season? \U0001f440\U0001f914\n\n@TheAndyK...
$ quoted_created_at       <dttm> NA, 2020-10-29 15:20:14, 2020-10-27 14:43:31, NA, NA, NA, 2020-10-29 1...
$ quoted_source           <chr> NA, "Twitter Web App", "Twitter Web App", NA, NA, NA, "Twitter Web App"...
$ quoted_favorite_count   <int> NA, 123, 21, NA, NA, NA, 123, NA, NA, NA, 123, 123, 123, 123, 123, NA, ...
$ quoted_retweet_count    <int> NA, 14, 7, NA, NA, NA, 14, NA, NA, NA, 14, 14, 14, 14, 14, NA, NA, 14, ...
$ quoted_user_id          <chr> NA, "202416362", "202416362", NA, NA, NA, "202416362", NA, NA, NA, "202...
$ quoted_screen_name      <chr> NA, "marchmadness", "marchmadness", NA, NA, NA, "marchmadness", NA, NA,...
$ quoted_name             <chr> NA, "NCAA March Madness", "NCAA March Madness", NA, NA, NA, "NCAA March...
$ quoted_followers_count  <int> NA, 1419268, 1419268, NA, NA, NA, 1419268, NA, NA, NA, 1419268, 1419268...
$ quoted_friends_count    <int> NA, 815, 815, NA, NA, NA, 815, NA, NA, NA, 815, 815, 815, 815, 815, NA,...
$ quoted_statuses_count   <int> NA, 29873, 29873, NA, NA, NA, 29873, NA, NA, NA, 29873, 29873, 29873, 2...
$ quoted_location         <chr> NA, "", "", NA, NA, NA, "", NA, NA, NA, "", "", "", "", "", NA, NA, "",...
$ quoted_description      <chr> NA, "The official NCAA March Madness destination for all things Divisio...
$ quoted_verified         <lgl> NA, TRUE, TRUE, NA, NA, NA, TRUE, NA, NA, NA, TRUE, TRUE, TRUE, TRUE, T...
$ retweet_status_id       <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_text            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_created_at      <dttm> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
$ retweet_source          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_favorite_count  <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_retweet_count   <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_user_id         <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_screen_name     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_name            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_followers_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_friends_count   <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_statuses_count  <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_location        <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_description     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ retweet_verified        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ place_url               <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "https://api.twitter.com/1....
$ place_name              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Texas", "Tucson", NA, NA, ...
$ place_full_name         <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Texas, USA", "Tucson, AZ",...
$ place_type              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "admin", "city", NA, NA, NA...
$ country                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "United States", "United St...
$ country_code            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "US", "US", NA, NA, NA, NA,...
$ geo_coords              <list> [<NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>,...
$ coords_coords           <list> [<NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>,...
$ bbox_coords             <list> [<NA, NA, NA, NA, NA, NA, NA, NA>, <NA, NA, NA, NA, NA, NA, NA, NA>, <...
$ status_url              <chr> "https://twitter.com/koenig_reese/status/1321838635323445248", "https:/...
$ name                    <chr> "Reese Koenig", "Andy Katz", "Andy Katz", "Andy Katz", "Andy Katz", "Ca...
$ location                <chr> "", "", "", "", "", "", "Spokane, WA", "", "United States", "", "Grand ...
$ description             <chr> "", "Digital reporter, analyst, host for @MarchMadness, March Madness 3...
$ url                     <chr> NA, NA, NA, NA, NA, NA, NA, NA, "https://t.co/L6kiKVvYn5", NA, NA, "htt...
$ protected               <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, F...
$ followers_count         <int> 1, 471484, 471484, 471484, 471484, 214, 3673, 17, 465, 61, 1259, 362, 2...
$ friends_count           <int> 135, 1387, 1387, 1387, 1387, 1096, 1194, 109, 1231, 1277, 1092, 574, 12...
$ listed_count            <int> 0, 6808, 6808, 6808, 6808, 2, 28, 0, 3, 0, 3, 1, 7, 0, 2, 1, 3, 244, 38...
$ statuses_count          <int> 2, 40140, 40140, 40140, 40140, 3651, 19731, 1086, 46054, 7047, 72039, 2...
$ favourites_count        <int> 6, 53, 53, 53, 53, 21105, 24639, 100, 69223, 1219, 30820, 9363, 36398, ...
$ account_created_at      <dttm> 2020-10-29 15:25:06, 2009-10-29 01:13:45, 2009-10-29 01:13:45, 2009-10...
$ verified                <lgl> FALSE, TRUE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE...
$ profile_url             <chr> NA, NA, NA, NA, NA, NA, NA, NA, "https://t.co/L6kiKVvYn5", NA, NA, "htt...
$ profile_expanded_url    <chr> NA, NA, NA, NA, NA, NA, NA, NA, "http://tonycastilla.com", NA, NA, "htt...
$ account_lang            <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,...
$ profile_banner_url      <chr> NA, "https://pbs.twimg.com/profile_banners/85961526/1535831124", "https...
$ profile_background_url  <chr> NA, "http://abs.twimg.com/images/themes/theme1/bg.png", "http://abs.twi...
$ profile_image_url       <chr> "http://pbs.twimg.com/profile_images/1321835514186604554/qwrBaNDJ_norma...
$ line                    <int> 1, 1, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 1, 1,...

"Because we have kept text such as hashtags and usernames in the dataset, we can’t use a simple anti_join() to remove stop words. Instead, we can take the approach shown in the filter() line that uses str_detect() from the stringr package. – https://www.tidytextmining.com/twitter.html

stop words

head(stopwordslangs)
tweets_tokenized %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  anti_join(stopwordslangs)           # anti_join(tidytext::get_stopwords())
Joining, by = "word"

Word frequencies

Calculate word frequency

frequency <- tweets_tokenized %>% 
  group_by(screen_name) %>% 
  count(word, sort = TRUE) %>% 
  left_join(tweets_tokenized %>% 
              group_by(screen_name) %>% 
              summarise(total = n())) %>%
  mutate(freq = n/total)
`summarise()` ungrouping output (override with `.groups` argument)
Joining, by = "screen_name"
frequency

"This is a nice and tidy data frame but we would actually like to plot those frequencies on the x- and y-axes of a plot, so we will need to use spread() from tidyr make a differently shaped data frame. – https://www.tidytextmining.com/twitter.html

pivot_wider

frequency <- frequency %>% 
  select(screen_name, word, freq) %>% 
  pivot_wider(names_from = screen_name, values_from = freq) #, values_fill = 0)

frequency

viz it

tweets_tokenized %>% 
  # group_by(screen_name) %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  anti_join(stopwordslangs)  %>% 
  wordcloud2()
Joining, by = "word"
tweets_tokenized %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  slice_head(n = 30) %>% 
  ggplot(aes(freq, fct_reorder(word, freq))) +
  geom_col()


tweets_tokenized %>% 
  count(word, sort = TRUE, name = "freq") %>% 
  anti_join(stopwordslangs) %>% 
  filter(!str_detect(word, "^\\@")) %>% 
  slice_head(n = 30) %>% 
  ggplot(aes(freq, fct_reorder(word, freq))) +
  geom_col()
Joining, by = "word"

ggplot(frequency, aes(LouInPain, Dukeballnation)) +
  geom_jitter(alpha = 0.1, size = 2.5, width = 0.25, height = 0.25) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) +
  scale_x_log10(labels = scales::percent_format()) +
  scale_y_log10(labels = scales::percent_format()) +
  geom_abline(color = "firebrick")


# fs::dir_create("images")
# ggsave("images/dukeball.png")

# "CBBCent1" | screen_name == "Adam_Bradford1
# marchmadness  TheAndyKatz

ggplot(frequency, aes(marchmadness, TheAndyKatz)) +
  geom_jitter(alpha = 0.1, size = 2.5, width = 0.25, height = 0.25) +
  geom_text(aes(label = word), check_overlap = TRUE, vjust = 1.5) +
  scale_x_log10(labels = scales::percent_format()) +
  scale_y_log10(labels = scales::percent_format()) +
  geom_abline(color = "firebrick")

Word Usage

tweets_by_tweeter %>% 
  summarise(min_date = min(created_at), max_date = max(created_at))
word_ratios <- tweets_tokenized %>%
  # filter(screen_name == "CBBCent1" | screen_name == "Adam_Bradford14") %>%
  filter(screen_name == "LouInPain" | screen_name == "Dukeballnation") %>% 
  filter(!str_detect(word, "^@")) %>%
  count(word, screen_name) %>%
  group_by(word) %>%
  filter(sum(n) >= 2) %>%
  ungroup() %>%
  pivot_wider(names_from = screen_name, values_from = n, values_fill = 0) %>%
  mutate_if(is.numeric, list(~(. + 1) / (sum(.) + 1))) %>%
  mutate(logratio = log(LouInPain / Dukeballnation)) %>%
  arrange(desc(logratio))

word_ratios

equal usage

word_ratios %>% 
  arrange(abs(logratio))
word_ratios %>%
  group_by(logratio < 0) %>%
  top_n(15, abs(logratio)) %>%
  ungroup() %>%
  mutate(word = reorder(word, logratio)) %>%
  ggplot(aes(word, logratio, fill = logratio < 0)) +
  geom_col() + #show.legend = FALSE) +
  coord_flip() +
  ylab("log odds ratio (CCBCent1/Adam_Bradford14)") +
  scale_fill_discrete(name = "", labels = c("LouInPain", "Dukeballnation"))

Favorites and retweets

https://www.tidytextmining.com/twitter.html#favorites-and-retweets

Changes in word use

https://www.tidytextmining.com/twitter.html#changes-in-word-use

Term Document Matrix

# dtm <- DocumentTermMatrix(docs) 

dtm2 <- TermDocumentMatrix(corpus)
m <- as.matrix(dtm2)
v <- sort(rowSums(m),decreasing=TRUE)
d <- data.frame(word = names(v),freq=v)

d <- d %>% 
  slice(2:200)

https://www.tidytextmining.com/tfidf.html#the-bind_tf_idf-function

tweet_words <- tweets_by_tweeter %>% 
  select(screen_name, text, status_id, user_id) %>%  
  unnest_tokens(word, text, token = "tweets") %>% 
  filter(!str_detect(word, "^\\@")) %>%
  filter(!str_detect(word, "^http")) %>%
  anti_join(stopwordslangs)  %>% 
  count(word, tweeter = screen_name, sort = TRUE)
Using `to_lower = TRUE` with `token = 'tweets'` may not preserve URLs.
Joining, by = "word"
tweet_words

total_words <- tweet_words %>%
  group_by(tweeter) %>%
  summarize(total = sum(n)) %>% 
  arrange(-total)
`summarise()` ungrouping output (override with `.groups` argument)
total_words

tweet_words <- left_join(tweet_words, total_words)
Joining, by = "tweeter"
tweet_words
tweet_words %>% 
  bind_tf_idf(word, tweeter, n)
tweet_words %>% 
  bind_tf_idf(word, tweeter, n) %>% 
  arrange(desc(tf_idf)) %>% 
  mutate(word = factor(word, levels = rev(unique(word)))) %>% 
  filter(n > 2) %>% 
  # group_by(tweeter) %>% 
  # top_n(2) %>% 
  # ungroup() %>% 
  ggplot(aes(word, tf_idf)) +
  geom_col() +
  facet_wrap(~ tweeter) +
  coord_flip()

Resource list

LS0tDQp0aXRsZTogIlJ0d2VldCINCnN1YnRpdGxlOiAiYW4gUmZ1biBkZW1vbnN0cmF0aW9uIg0KYXV0aG9yOiAiSm9obiBMaXR0bGUiDQpkYXRlOiAnYHIgU3lzLkRhdGUoKWAnDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vDQoNCiMjIGxvYWQgbGlicmFyaWVzDQoNCmBgYHtyIGxpYnJhcmllcywgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocnR3ZWV0KQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KbGlicmFyeSh3b3JkY2xvdWQyKQ0KYGBgDQoNCg0KIyMgR2V0IFR3ZWV0cyANCg0Kc2VhcmNoX3R3ZWV0cw0KYGBge3IgZ2V0VHdlZXRzfQ0KdHdlZXRfY29sbGVjdGlvbiA8LSBzZWFyY2hfdHdlZXRzKCJtYXJjaG1hZG5lc3MiLCBuPTEwMDAsIGxhbmcgPSAiZW4iKQ0KdHdlZXRfY29sbGVjdGlvbiA8LSB0d2VldF9jb2xsZWN0aW9uICU+JSANCiAgZmlsdGVyKGlzX3JldHdlZXQgPT0gIkZBTFNFIikNCnR3ZWV0X2NvbGxlY3Rpb24NCmBgYA0KDQoNCiMjIFRva2VuaXplIHR3ZWV0cw0KDQpgYGB7ciBjb3JwdXMydmVjdG9yfQ0KdHdlZXRzX2J5X3R3ZWV0ZXIgPC0gdHdlZXRfY29sbGVjdGlvbiAlPiUgDQogIGdyb3VwX2J5KHNjcmVlbl9uYW1lKSAlPiUgDQogIG11dGF0ZShsaW5lID0gcm93X251bWJlcigpKSAlPiUgDQogIHVuZ3JvdXAoKQ0KDQp0d2VldHNfYnlfdHdlZXRlciAlPiUgDQogIGNvdW50KHNjcmVlbl9uYW1lLCBzb3J0ID0gVFJVRSkNCg0KZ2xpbXBzZSh0d2VldHNfYnlfdHdlZXRlcikNCmBgYA0KDQoNCj4gIkJlY2F1c2Ugd2UgaGF2ZSBrZXB0IHRleHQgc3VjaCBhcyBoYXNodGFncyBhbmQgdXNlcm5hbWVzIGluIHRoZSBkYXRhc2V0LCB3ZSBjYW7igJl0IHVzZSBhIHNpbXBsZSBhbnRpX2pvaW4oKSB0byByZW1vdmUgc3RvcCB3b3Jkcy4gSW5zdGVhZCwgd2UgY2FuIHRha2UgdGhlIGFwcHJvYWNoIHNob3duIGluIHRoZSBmaWx0ZXIoKSBsaW5lIHRoYXQgdXNlcyBzdHJfZGV0ZWN0KCkgZnJvbSB0aGUgc3RyaW5nciBwYWNrYWdlLiAtLSBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vdHdpdHRlci5odG1sDQoNCg0KYGBge3IgdG9rZW5pemVkIHR3ZWV0c30NCnR3ZWV0c190b2tlbml6ZWQgPC0gdHdlZXRzX2J5X3R3ZWV0ZXIgJT4lIA0KICBzZWxlY3QodGV4dCwgc2NyZWVuX25hbWUsIGxpbmUpICU+JSANCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0LCB0b2tlbiA9ICJ0d2VldHMiKSAlPiUNCiAgZmlsdGVyKCF3b3JkICVpbiUgc3RvcF93b3JkcyR3b3JkLA0KICAgICAgICAgIXdvcmQgJWluJSBzdHJfcmVtb3ZlX2FsbChzdG9wX3dvcmRzJHdvcmQsICInIiksDQogICAgICAgICBzdHJfZGV0ZWN0KHdvcmQsICJbYS16XSIpKSANCg0KdHdlZXRzX3Rva2VuaXplZA0KYGBgDQoNCiMjIHN0b3Agd29yZHMNCg0KYGBge3J9DQpoZWFkKHN0b3B3b3Jkc2xhbmdzKQ0KYGBgDQoNCg0KYGBge3J9DQp0d2VldHNfdG9rZW5pemVkICU+JSANCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUsIG5hbWUgPSAiZnJlcSIpICU+JSANCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KHdvcmQsICJeXFxAIikpICU+JSANCiAgYW50aV9qb2luKHN0b3B3b3Jkc2xhbmdzKSAgICAgICAgICAgIyBhbnRpX2pvaW4odGlkeXRleHQ6OmdldF9zdG9wd29yZHMoKSkNCmBgYA0KDQoNCg0KDQojIyBXb3JkIGZyZXF1ZW5jaWVzDQoNCiMjIyBDYWxjdWxhdGUgd29yZCBmcmVxdWVuY3kNCg0KYGBge3J9DQpmcmVxdWVuY3kgPC0gdHdlZXRzX3Rva2VuaXplZCAlPiUgDQogIGdyb3VwX2J5KHNjcmVlbl9uYW1lKSAlPiUgDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgDQogIGxlZnRfam9pbih0d2VldHNfdG9rZW5pemVkICU+JSANCiAgICAgICAgICAgICAgZ3JvdXBfYnkoc2NyZWVuX25hbWUpICU+JSANCiAgICAgICAgICAgICAgc3VtbWFyaXNlKHRvdGFsID0gbigpKSkgJT4lDQogIG11dGF0ZShmcmVxID0gbi90b3RhbCkNCg0KZnJlcXVlbmN5DQpgYGANCg0KPiAiVGhpcyBpcyBhIG5pY2UgYW5kIHRpZHkgZGF0YSBmcmFtZSBidXQgd2Ugd291bGQgYWN0dWFsbHkgbGlrZSB0byBwbG90IHRob3NlIGZyZXF1ZW5jaWVzIG9uIHRoZSB4LSBhbmQgeS1heGVzIG9mIGEgcGxvdCwgc28gd2Ugd2lsbCBuZWVkIHRvIHVzZSBzcHJlYWQoKSBmcm9tIHRpZHlyIG1ha2UgYSBkaWZmZXJlbnRseSBzaGFwZWQgZGF0YSBmcmFtZS4gLS0gaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3R3aXR0ZXIuaHRtbA0KDQpwaXZvdF93aWRlcg0KDQpgYGB7cn0NCmZyZXF1ZW5jeSA8LSBmcmVxdWVuY3kgJT4lIA0KICBzZWxlY3Qoc2NyZWVuX25hbWUsIHdvcmQsIGZyZXEpICU+JSANCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNjcmVlbl9uYW1lLCB2YWx1ZXNfZnJvbSA9IGZyZXEpICMsIHZhbHVlc19maWxsID0gMCkNCg0KZnJlcXVlbmN5DQpgYGANCg0KIyMjIHZpeiBpdA0KDQpgYGB7ciB3b3JkLWNsb3VkfQ0KdHdlZXRzX3Rva2VuaXplZCAlPiUgDQogICMgZ3JvdXBfYnkoc2NyZWVuX25hbWUpICU+JSANCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUsIG5hbWUgPSAiZnJlcSIpICU+JSANCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KHdvcmQsICJeXFxAIikpICU+JSANCiAgYW50aV9qb2luKHN0b3B3b3Jkc2xhbmdzKSAgJT4lIA0KICB3b3JkY2xvdWQyKCkNCg0KYGBgDQpgYGB7ciB3b3JkLWZyZXEgYmFycGxvdCwgZmlnLmhlaWdodD03fQ0KdHdlZXRzX3Rva2VuaXplZCAlPiUgDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFLCBuYW1lID0gImZyZXEiKSAlPiUgDQogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCAiXlxcQCIpKSAlPiUgDQogIHNsaWNlX2hlYWQobiA9IDMwKSAlPiUgDQogIGdncGxvdChhZXMoZnJlcSwgZmN0X3Jlb3JkZXIod29yZCwgZnJlcSkpKSArDQogIGdlb21fY29sKCkNCg0KdHdlZXRzX3Rva2VuaXplZCAlPiUgDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFLCBuYW1lID0gImZyZXEiKSAlPiUgDQogIGFudGlfam9pbihzdG9wd29yZHNsYW5ncykgJT4lIA0KICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgIl5cXEAiKSkgJT4lIA0KICBzbGljZV9oZWFkKG4gPSAzMCkgJT4lIA0KICBnZ3Bsb3QoYWVzKGZyZXEsIGZjdF9yZW9yZGVyKHdvcmQsIGZyZXEpKSkgKw0KICBnZW9tX2NvbCgpDQpgYGANCg0KDQpgYGB7ciB3b3JkX2ZyZXEgcGxvdH0NCmdncGxvdChmcmVxdWVuY3ksIGFlcyhMb3VJblBhaW4sIER1a2ViYWxsbmF0aW9uKSkgKw0KICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSwgc2l6ZSA9IDIuNSwgd2lkdGggPSAwLjI1LCBoZWlnaHQgPSAwLjI1KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSB3b3JkKSwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHZqdXN0ID0gMS41KSArDQogIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIHNjYWxlX3lfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIGdlb21fYWJsaW5lKGNvbG9yID0gImZpcmVicmljayIpDQoNCiMgZnM6OmRpcl9jcmVhdGUoImltYWdlcyIpDQojIGdnc2F2ZSgiaW1hZ2VzL2R1a2ViYWxsLnBuZyIpDQoNCiMgIkNCQkNlbnQxIiB8IHNjcmVlbl9uYW1lID09ICJBZGFtX0JyYWRmb3JkMQ0KIyBtYXJjaG1hZG5lc3MgIFRoZUFuZHlLYXR6DQoNCmdncGxvdChmcmVxdWVuY3ksIGFlcyhtYXJjaG1hZG5lc3MsIFRoZUFuZHlLYXR6KSkgKw0KICBnZW9tX2ppdHRlcihhbHBoYSA9IDAuMSwgc2l6ZSA9IDIuNSwgd2lkdGggPSAwLjI1LCBoZWlnaHQgPSAwLjI1KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSB3b3JkKSwgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIHZqdXN0ID0gMS41KSArDQogIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIHNjYWxlX3lfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIGdlb21fYWJsaW5lKGNvbG9yID0gImZpcmVicmljayIpDQpgYGANCg0KDQojIyBXb3JkIFVzYWdlDQoNCg0KYGBge3J9DQp0d2VldHNfYnlfdHdlZXRlciAlPiUgDQogIHN1bW1hcmlzZShtaW5fZGF0ZSA9IG1pbihjcmVhdGVkX2F0KSwgbWF4X2RhdGUgPSBtYXgoY3JlYXRlZF9hdCkpDQpgYGANCg0KDQpgYGB7cn0NCndvcmRfcmF0aW9zIDwtIHR3ZWV0c190b2tlbml6ZWQgJT4lDQogICMgZmlsdGVyKHNjcmVlbl9uYW1lID09ICJDQkJDZW50MSIgfCBzY3JlZW5fbmFtZSA9PSAiQWRhbV9CcmFkZm9yZDE0IikgJT4lDQogIGZpbHRlcihzY3JlZW5fbmFtZSA9PSAiTG91SW5QYWluIiB8IHNjcmVlbl9uYW1lID09ICJEdWtlYmFsbG5hdGlvbiIpICU+JSANCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KHdvcmQsICJeQCIpKSAlPiUNCiAgY291bnQod29yZCwgc2NyZWVuX25hbWUpICU+JQ0KICBncm91cF9ieSh3b3JkKSAlPiUNCiAgZmlsdGVyKHN1bShuKSA+PSAyKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gc2NyZWVuX25hbWUsIHZhbHVlc19mcm9tID0gbiwgdmFsdWVzX2ZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGxpc3QofiguICsgMSkgLyAoc3VtKC4pICsgMSkpKSAlPiUNCiAgbXV0YXRlKGxvZ3JhdGlvID0gbG9nKExvdUluUGFpbiAvIER1a2ViYWxsbmF0aW9uKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhsb2dyYXRpbykpDQoNCndvcmRfcmF0aW9zDQpgYGANCg0KIyMjIGVxdWFsIHVzYWdlDQoNCmBgYHtyfQ0Kd29yZF9yYXRpb3MgJT4lIA0KICBhcnJhbmdlKGFicyhsb2dyYXRpbykpDQpgYGANCg0KDQpgYGB7ciB3b3JkLXVzYWdlfQ0Kd29yZF9yYXRpb3MgJT4lDQogIGdyb3VwX2J5KGxvZ3JhdGlvIDwgMCkgJT4lDQogIHRvcF9uKDE1LCBhYnMobG9ncmF0aW8pKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbG9ncmF0aW8pKSAlPiUNCiAgZ2dwbG90KGFlcyh3b3JkLCBsb2dyYXRpbywgZmlsbCA9IGxvZ3JhdGlvIDwgMCkpICsNCiAgZ2VvbV9jb2woKSArICNzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHlsYWIoImxvZyBvZGRzIHJhdGlvIChDQ0JDZW50MS9BZGFtX0JyYWRmb3JkMTQpIikgKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWUgPSAiIiwgbGFiZWxzID0gYygiTG91SW5QYWluIiwgIkR1a2ViYWxsbmF0aW9uIikpDQpgYGANCg0KDQojIyBGYXZvcml0ZXMgYW5kIHJldHdlZXRzDQoNCmh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS90d2l0dGVyLmh0bWwjZmF2b3JpdGVzLWFuZC1yZXR3ZWV0cw0KDQojIyBDaGFuZ2VzIGluIHdvcmQgdXNlDQoNCmh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS90d2l0dGVyLmh0bWwjY2hhbmdlcy1pbi13b3JkLXVzZQ0KDQojIyBUZXJtIERvY3VtZW50IE1hdHJpeA0KDQpgYGB7cn0NCiMgZHRtIDwtIERvY3VtZW50VGVybU1hdHJpeChkb2NzKSANCg0KZHRtMiA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzKQ0KbSA8LSBhcy5tYXRyaXgoZHRtMikNCnYgPC0gc29ydChyb3dTdW1zKG0pLGRlY3JlYXNpbmc9VFJVRSkNCmQgPC0gZGF0YS5mcmFtZSh3b3JkID0gbmFtZXModiksZnJlcT12KQ0KDQpkIDwtIGQgJT4lIA0KICBzbGljZSgyOjIwMCkNCg0KYGBgDQoNCmh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS90ZmlkZi5odG1sI3RoZS1iaW5kX3RmX2lkZi1mdW5jdGlvbg0KDQpgYGB7cn0NCnR3ZWV0X3dvcmRzIDwtIHR3ZWV0c19ieV90d2VldGVyICU+JSANCiAgc2VsZWN0KHNjcmVlbl9uYW1lLCB0ZXh0LCBzdGF0dXNfaWQsIHVzZXJfaWQpICU+JSAgDQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCwgdG9rZW4gPSAidHdlZXRzIikgJT4lIA0KICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgIl5cXEAiKSkgJT4lDQogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCAiXmh0dHAiKSkgJT4lDQogIGFudGlfam9pbihzdG9wd29yZHNsYW5ncykgICU+JSANCiAgY291bnQod29yZCwgdHdlZXRlciA9IHNjcmVlbl9uYW1lLCBzb3J0ID0gVFJVRSkNCg0KdHdlZXRfd29yZHMNCg0KdG90YWxfd29yZHMgPC0gdHdlZXRfd29yZHMgJT4lDQogIGdyb3VwX2J5KHR3ZWV0ZXIpICU+JQ0KICBzdW1tYXJpemUodG90YWwgPSBzdW0obikpICU+JSANCiAgYXJyYW5nZSgtdG90YWwpDQoNCnRvdGFsX3dvcmRzDQoNCnR3ZWV0X3dvcmRzIDwtIGxlZnRfam9pbih0d2VldF93b3JkcywgdG90YWxfd29yZHMpDQoNCnR3ZWV0X3dvcmRzDQpgYGANCg0KYGBge3J9DQp0d2VldF93b3JkcyAlPiUgDQogIGJpbmRfdGZfaWRmKHdvcmQsIHR3ZWV0ZXIsIG4pDQpgYGANCg0KYGBge3IgZmlnLmhlaWdodD0xMn0NCnR3ZWV0X3dvcmRzICU+JSANCiAgYmluZF90Zl9pZGYod29yZCwgdHdlZXRlciwgbikgJT4lIA0KICBhcnJhbmdlKGRlc2ModGZfaWRmKSkgJT4lIA0KICBtdXRhdGUod29yZCA9IGZhY3Rvcih3b3JkLCBsZXZlbHMgPSByZXYodW5pcXVlKHdvcmQpKSkpICU+JSANCiAgZmlsdGVyKG4gPiAyKSAlPiUgDQogICMgZ3JvdXBfYnkodHdlZXRlcikgJT4lIA0KICAjIHRvcF9uKDIpICU+JSANCiAgIyB1bmdyb3VwKCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHdvcmQsIHRmX2lkZikpICsNCiAgZ2VvbV9jb2woKSArDQogIGZhY2V0X3dyYXAofiB0d2VldGVyKSArDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCg0KDQoNCg0KIyMgUmVzb3VyY2UgbGlzdA0KDQotIGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS90ZXh0LW1pbmluZy1hbmQtd29yZC1jbG91ZC1mdW5kYW1lbnRhbHMtaW4tci01LXNpbXBsZS1zdGVwcy15b3Utc2hvdWxkLWtub3cNCg0KLSBodHRwOi8vYW50b25pby1mZXJyYXJvLmV1LnBuL3dvcmQtY2xvdWRzLWluLXItcGFja2FnZXMtd29yZGNsb3VkMi1hbmQtdG0vDQoNCi0gaHR0cHM6Ly9qcm5vbGQuZ2l0aHViLmlvL3Fzcy10aWR5L2Rpc2NvdmVyeS5odG1sI3RleHR1YWwtZGF0YQ0KDQotIGh0dHBzOi8vcnN0dWRpby1wdWJzLXN0YXRpYy5zMy5hbWF6b25hd3MuY29tLzMxODY3XzgyMzY5ODdjZjBhODQ0NGU5NjJjY2QyYWVjNDZkOWMzLmh0bWwNCg0KLSBvZiBsZXNzIHVzZQ0KDQogICAgLSBodHRwOi8vd3d3LmNvb2tib29rLXIuY29tL01hbmlwdWxhdGluZ19kYXRhL0NvbnZlcnRpbmdfYmV0d2Vlbl9kYXRhX2ZyYW1lc19hbmRfY29udGluZ2VuY3lfdGFibGVzLw0KICAgIC0gaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vaG93LXRvLWdldC10aGUtZnJlcXVlbmN5LXRhYmxlLW9mLWEtY2F0ZWdvcmljYWwtdmFyaWFibGUtYXMtYS1kYXRhLWZyYW1lLWluLXIvDQogICAgLSBodHRwczovL3d3dy5xdW9yYS5jb20vSG93LWRvLUktZ2V0LWEtZnJlcXVlbmN5LWNvdW50LWJhc2VkLW9uLXR3by1jb2x1bW5zLXZhcmlhYmxlcy1pbi1hbi1SLWRhdGFmcmFtZQ0KICAgIC0gaHR0cHM6Ly93d3cucXVvcmEuY29tL0hvdy1kby15b3UtY3JlYXRlLWEtY29ycHVzLWZyb20tYS1kYXRhLWZyYW1lLWluLVINCiAgICANCg0K